﻿
package extremefx.tools {
	import flash.display.InteractiveObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.ContextMenuEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.net.LocalConnection;
	import flash.system.System;
	import flash.ui.ContextMenu;
	import flash.ui.ContextMenuItem;
	import flash.utils.getTimer;	

	/**
	 * @author Marcelo Volmaro
	 */
	public final class Profiler {
		private static var _itvTime : int;
		private static var _initTime : int;
		private static var _currentTime : int;
		private static var _frameCount : int;
		private static var _totalCount : int;

		public static var _minFps : Number;
		public static var _maxFps : Number;
		public static var _minMem : Number;
		public static var _maxMem : Number;
		public static var _history : int = 60;
		public static var _fpsList : Array = [];
		public static var _memList : Array = [];

		private static var _displayed : Boolean = false;
		private static var _started : Boolean = false;
		private static var _inited : Boolean = false;
		private static var _frame : Sprite;
		private static var _stage : Stage;
		private static var _content : ProfilerContent;
		private static var _ci : ContextMenuItem;
		
		public static function init(swf : Stage, context : InteractiveObject) : void {
			if(_inited) return;
			
			_inited = true;
			_stage = swf;
			
			_content = new ProfilerContent();
			_frame = new Sprite();
			
			_minFps = Number.MAX_VALUE;
			_maxFps = Number.MIN_VALUE;
			_minMem = Number.MAX_VALUE;
			_maxMem = Number.MIN_VALUE;
			
			var cm : ContextMenu = new ContextMenu();
			cm.hideBuiltInItems();
			_ci = new ContextMenuItem("Show Profiler", true);
			addEvent(_ci, ContextMenuEvent.MENU_ITEM_SELECT, onSelect);
			cm.customItems = [_ci];
			context.contextMenu = cm;
			
			start();
		}

		public static function start() : void {
			if(_started) return;
			
			_started = true;
			_initTime = _itvTime = getTimer();
			_totalCount = _frameCount = 0;
			
			addEvent(_frame, Event.ENTER_FRAME, draw);
		}

		public static function stop() : void {
			if(!_started) return;
			
			_started = false;
			
			removeEvent(_frame, Event.ENTER_FRAME, draw);
		}

		public static function gc() : void {
			try {
				new LocalConnection().connect('foo');
				new LocalConnection().connect('foo');
			} catch (e : Error) {
			}
		}

		public static function get currentFps() : Number {
			return _frameCount / intervalTime;
		}

		public static function get currentMem() : Number {
			return (System.totalMemory / 1024) / 1000;
		}

		public static function get averageFps() : Number {
			return _totalCount / runningTime;
		}

		private static function get runningTime() : Number {
			return (_currentTime - _initTime) / 1000;
		}

		private static function get intervalTime() : Number {
			return (_currentTime - _itvTime) / 1000;
		}

		
		private static function onSelect(e : ContextMenuEvent) : void {
			if(!_displayed) {
				show();
			} else {
				hide();
			}
		}

		private static function show() : void {
			_ci.caption = "Hide Profiler";
			_displayed = true;
			addEvent(_stage, Event.RESIZE, resize);
			_stage.addChild(_content);
			updateDisplay();
		}

		private static function hide() : void {
			_ci.caption = "Show Profiler";
			_displayed = false;
			removeEvent(_stage, Event.RESIZE, resize);
			_stage.removeChild(_content);
		}
		
		private static function resize(e:Event) : void {
			_content.update(runningTime, _minFps, _maxFps, _minMem, _maxMem, currentFps, currentMem, averageFps, _fpsList, _memList, _history);
		}
		
		private static function draw(e : Event) : void {
			_currentTime = getTimer();
			
			_frameCount++;
			_totalCount++;

			if(intervalTime >= 1) {
				if(_displayed) {
					updateDisplay();
				} else {
					updateMinMax();
				}
				
				_fpsList.unshift(currentFps);
				_memList.unshift(currentMem);
				
				if(_fpsList.length > _history) _fpsList.pop();
				if(_memList.length > _history) _memList.pop();
				
				_itvTime = _currentTime;
				_frameCount = 0;
			}
		}

		private static function updateDisplay() : void {
			updateMinMax();
			_content.update(runningTime, _minFps, _maxFps, _minMem, _maxMem, currentFps, currentMem, averageFps, _fpsList, _memList, _history);
		}

		private static function updateMinMax() : void {
			_minFps = Math.min(currentFps, _minFps);
			_maxFps = Math.max(currentFps, _maxFps);
				
			_minMem = Math.min(currentMem, _minMem);
			_maxMem = Math.max(currentMem, _maxMem);
		}

		private static function addEvent(item : EventDispatcher, type : String, listener : Function) : void {
			item.addEventListener(type, listener, false, 0, true);
		}

		private static function removeEvent(item : EventDispatcher, type : String, listener : Function) : void {
			item.removeEventListener(type, listener);
		}
	}
}

import flash.display.*;
import flash.events.Event;
import flash.text.*;

final class ProfilerContent extends Sprite {

	private var _minFpsTxtBx : TextField;
	private var _maxFpsTxtBx : TextField;
	private var _minMemTxtBx : TextField;
	private var _maxMemTxtBx : TextField;
	private var _infoTxtBx : TextField;
	private var _box : Shape;
	private var _fps : Shape;
	private var _mb : Shape;

	public function ProfilerContent() : void {
		_fps = new Shape();
		_mb = new Shape();
		_box = new Shape();
			
		mouseChildren = false;
		mouseEnabled = false;
			
		_fps.x = 65;
		_fps.y = 45;	
		_mb.x = 65;
		_mb.y = 90;
			
		var tf : TextFormat = new TextFormat("_sans", 9, 0xAAAAAA);
			
		_infoTxtBx = new TextField();
		_infoTxtBx.autoSize = TextFieldAutoSize.LEFT;
		_infoTxtBx.defaultTextFormat = new TextFormat("_sans", 11, 0xCCCCCC);
		_infoTxtBx.y = 98;
			
		_minFpsTxtBx = new TextField();
		_minFpsTxtBx.autoSize = TextFieldAutoSize.LEFT;
		_minFpsTxtBx.defaultTextFormat = tf;
		_minFpsTxtBx.x = 7;
		_minFpsTxtBx.y = 37;
			
		_maxFpsTxtBx = new TextField();
		_maxFpsTxtBx.autoSize = TextFieldAutoSize.LEFT;
		_maxFpsTxtBx.defaultTextFormat = tf;
		_maxFpsTxtBx.x = 7;
		_maxFpsTxtBx.y = 5;
			
		_minMemTxtBx = new TextField();
		_minMemTxtBx.autoSize = TextFieldAutoSize.LEFT;
		_minMemTxtBx.defaultTextFormat = tf;
		_minMemTxtBx.x = 7;
		_minMemTxtBx.y = 83;
			
		_maxMemTxtBx = new TextField();
		_maxMemTxtBx.autoSize = TextFieldAutoSize.LEFT;
		_maxMemTxtBx.defaultTextFormat = tf;
		_maxMemTxtBx.x = 7;
		_maxMemTxtBx.y = 50;
			
		addChild(_box);
		addChild(_infoTxtBx);
		addChild(_minFpsTxtBx);
		addChild(_maxFpsTxtBx);
		addChild(_minMemTxtBx);
		addChild(_maxMemTxtBx);
		addChild(_fps);
		addChild(_mb);
		
		addEventListener(Event.ADDED_TO_STAGE, added, false, 0, true);
		addEventListener(Event.REMOVED_FROM_STAGE, removed, false, 0, true);
	}

	public function update(runningTime : Number, minFps : Number, maxFps : Number, minMem : Number, maxMem : Number, currentFps : Number, currentMem : Number, averageFps : Number, fpsList : Array, memList : Array, history : int) : void {
		if(runningTime >= 1) {
			_minFpsTxtBx.text = minFps.toFixed(3) + " Fps";
			_maxFpsTxtBx.text = maxFps.toFixed(3) + " Fps";
			_minMemTxtBx.text = minMem.toFixed(3) + " Mb";
			_maxMemTxtBx.text = maxMem.toFixed(3) + " Mb";
		}
			
		_infoTxtBx.text = "Current Fps " + currentFps.toFixed(3) + "   |   Average Fps " + averageFps.toFixed(3) + "   |   Memory Used " + currentMem.toFixed(3) + " Mb";
		_infoTxtBx.x = stage.stageWidth - _infoTxtBx.width - 20;
		
		var vec : Graphics = _fps.graphics;
		vec.clear();
		vec.lineStyle(1, 0x33FF00, 0.7);
			
		var i : int = 0;
		var len : int = fpsList.length;
		var height : int = 35;
		var width : int = stage.stageWidth - 80;
		var inc : Number = width / (history - 1);
		var rateRange : Number = maxFps - minFps;
		var value : Number;
			
		for(i = 0;i < len; i++) {
			value = (fpsList[i] - minFps) / rateRange;
			if(i == 0) {
				vec.moveTo(0, -value * height);
			} else {
				vec.lineTo(i * inc, -value * height);
			}
		}
			
		vec = _mb.graphics;
		vec.clear();
		vec.lineStyle(1, 0x0066FF, 0.7);
			
		i = 0;
		len = memList.length;
		rateRange = maxMem - minMem;
		for(i = 0;i < len; i++) {
			value = (memList[i] - minMem) / rateRange;
			if(i == 0) {
				vec.moveTo(0, -value * height);
			} else {
				vec.lineTo(i * inc, -value * height);
			}
		}
	}

	private function added(e : Event) : void {
		resize();
		stage.addEventListener(Event.RESIZE, resize, false, 0, true);
	}

	private function removed(e : Event) : void {
		stage.removeEventListener(Event.RESIZE, resize);
	}

	private function resize(e : Event = null) : void {
		var vec : Graphics = _box.graphics;
		vec.clear();
		
		vec.beginFill(0x000000, 0.5);
		vec.drawRect(0, 0, stage.stageWidth, 120);
		vec.lineStyle(1, 0xFFFFFF, 0.2);
			
		vec.moveTo(65, 45);
		vec.lineTo(65, 10);
		vec.moveTo(65, 45);
		vec.lineTo(stage.stageWidth - 15, 45);
			
		vec.moveTo(65, 90);
		vec.lineTo(65, 55);
		vec.moveTo(65, 90);
		vec.lineTo(stage.stageWidth - 15, 90);
			
		vec.endFill();
		
		_infoTxtBx.x = stage.stageWidth - _infoTxtBx.width - 20;
	}
}
